#!/usr/bin/env python3

import os, json, time, logging, yaml, requests
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime, timedelta

import schedule

LOG_DIR = "logs";
os.makedirs(LOG_DIR, exist_ok=True)
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
    handlers=[logging.FileHandler(f"{LOG_DIR}/monitor.log", encoding="utf-8"),
              logging.StreamHandler()]
)

# 读配置
# ================
CFG = yaml.safe_load(open("config.yaml", encoding="utf-8"))
GLOBAL = CFG["global"]
# 企业微信机器人
WECHAT_WEBHOOK = GLOBAL["wechat_webhook"]
if not WECHAT_WEBHOOK:
    raise ValueError("请设置环境变量 WECHAT_WEBHOOK")

INTERVAL = int(GLOBAL["interval"] or 30)
FAIL_THRES = int(GLOBAL["fail_threshold"] or 3)

# 告警去重：{"svc/inst": 首次告警时间}
sent_alerts = {}
ALERT_TTL = timedelta(hours=1)

# 计数器：service -> instance_id -> int
counter = {}
# ====================


def should_alert(key: str) -> bool:
    """True 表示可以发，False 表示 1h 内已发过"""
    now = datetime.now()
    first = sent_alerts.get(key)
    if first and now - first < ALERT_TTL:
        return False
    sent_alerts[key] = now
    # 定期清理，防止内存无限增长
    expired = [k for k, v in sent_alerts.items() if now - v >= ALERT_TTL]
    for k in expired:
        sent_alerts.pop(k, None)
    return True


def send_wechat_unique(key: str, msg: str):
    if not should_alert(key):
        logging.debug("告警去重：%s", key)
        return
    try:
        requests.post(WECHAT_WEBHOOK, json={"msgtype": "text", "text": {"content": msg}}, timeout=5)
        logging.info("已发送企微告警：%s", key)
    except Exception as e:
        logging.error("send wechat error: %s", e)


def check_one(item):
    svc, inst = item["service"], item["instance"]
    url = f"http://{inst['ip']}:{inst['port']}{svc['health_path']}"
    name = f"{svc['name']}/{inst['ip']}"
    try:
        r = requests.get(url, timeout=3)
        # ok = r.status_code == 200 and "UP" in r.text
        # 1) 状态码
        if r.status_code != 200:
            raise ValueError(f"HTTP {r.status_code}")
        # 2) 解析 JSON 并判断 result
        data = r.json()
        if data.get("result") != 0:
            raise ValueError(f"result={data.get('result')}")
        ok = True
    except Exception as e:
        ok = False
    key = (svc["name"], inst["ip"])
    if ok:
        if counter.get(key, 0) >= FAIL_THRES:
            send_wechat_unique(key, f"✅ 恢复 {name}  {datetime.now():%H:%M:%S}")
        counter[key] = 0
    else:
        counter[key] = counter.get(key, 0) + 1
        if counter[key] == FAIL_THRES:
            send_wechat_unique(key, f"⚠️ 告警 {name} 连续{FAIL_THRES}次探测失败 {datetime.now():%H:%M:%S}")
    return name, ok


def job():
    tasks = []
    for svc in CFG["services"]:
        for inst in svc["instances"]:
            tasks.append({"service": svc, "instance": inst})
    with ThreadPoolExecutor(max_workers=20) as pool:
        futures = [pool.submit(check_one, t) for t in tasks]
        for f in as_completed(futures):
            name, ok = f.result()
            logging.debug("%s -> %s", name, "OK" if ok else "FAIL")


if __name__ == "__main__":
    print("LOADED FROM:", schedule.__file__)
    print("CWD        :", os.getcwd())
    print("sys.path[0]:", sys.path[0])
    logging.info("monitor start")
    job()
    schedule.every(INTERVAL).seconds.do(job)
    while True:
        schedule.run_pending()
        time.sleep(1)
